# George Donnelly 106338300
# CSE 307 Assignment 2

import sys
import tpg

class EvalError(Exception):
    """Class of exceptions raised when an error occurs during evaluation."""

# These are the classes of nodes of our abstract syntax trees (ASTs).

class Node(object):
    """Base class of AST nodes.  May come in handy in the future."""
    
    def eval(self):
        """Evaluate the AST node, called on nodes of subclasses of Node."""
        raise Exception("Not implemented.")

# Evaluate the Integer from the parser
class Int(Node):

    def __init__(self, value):
        self.value = value
        
    def eval(self):
        return int(self.value)

# Evaluate the String from the parser
class String(Node):


    def __init__(self, value):
        self.value = value.strip('"')
    
    def eval(self):
        return str(self.value)

# Evaluate the left and right side of node and add the values
# This also checks if a line is trying to append a string
class Add(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        
        #check to see if they are different types
        if isinstance(right,int) and isinstance(left,str): raise EvalError()
        if isinstance(left,int) and isinstance(right,str): raise EvalError()

        fullString  = ''
        if not isinstance(left,int) and isinstance(left,str):
            fullString += left
            fullString = fullString.strip('"')
        if not isinstance(right,int) and isinstance(right,str):
            fullString += right
            fullString = fullString.strip('"')
            return fullString
        if not isinstance(left,int) and not isinstance(left,str): raise EvalError()
        if not isinstance(right,int) and not isinstance(right,str): raise EvalError()

        return int(left + right)

#Evaluate the left and right side of node and subtract the values
class Subtract(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left,int): raise EvalError()
        if not isinstance(right,int): raise EvalError()
        return int(left - right)

# Evaluate the right side of the node and run the NOT operator
class Not(Node):

    def __init__(self, right):
        # The nodes representing the left and right sides of this operation.
        self.right = right

    def eval(self):
        right = self.right.eval()
        if not isinstance(right,int): raise EvalError()
        return int( not right)


# Evaluate the left and right side of node and AND the values
class And(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left,int): raise EvalError()
        if not isinstance(right,int): raise EvalError()
        return int(left and right)

#Evauate the left and right side of node and OR the operation
class Or(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left,int): raise EvalError()
        if not isinstance(right,int): raise EvalError()
        return int(left or right)

# Evaluate the left and right side of node and multiply the values.
class Multiply(Node):
    
    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left,int): raise EvalError()
        if not isinstance(right,int): raise EvalError()
        return int(left * right)

# Evaluate the left and right side of node and multipply the values.
class Divide(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left, int): raise EvalError()
        if not isinstance(right, int): raise EvalError()
        if right == 0: raise EvalError()
        return int(left / right)

class Less(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left, int): raise EvalError()
        if not isinstance(right, int): raise EvalError()
        return int(left < right)

class Greater(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left, int): raise EvalError()
        if not isinstance(right, int): raise EvalError()
        return int(left > right)

class NotEqual(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left, int): raise EvalError()
        if not isinstance(right, int): raise EvalError()
        return int(left != right)   

class Equal(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left, int): raise EvalError()
        if not isinstance(right, int): raise EvalError()
        return int(left == right)

class DoList(Node):

    def __init__(self,thelist):
        # The nodes representing the left and right sides of this operation.
        self.thelist = thelist

    def eval(self):
        self.thelist = [x.eval() for x in self.thelist]
        return self.thelist

class GetChar(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(left, str): raise EvalError()
        if not isinstance(right, int): raise EvalError()
        return left[right]

class GetIndexList(Node):

    def __init__(self, left, right):
        # The nodes representing the left and right sides of this operation.
        self.left = left
        self.right = right

    def eval(self):
        left = self.left.eval()
        right = self.right.eval()
        if not isinstance(right, int): raise EvalError()
        if isinstance(left, list) or isinstance(left,str):
            return (left[right])
        else:
            raise EvalError()

# This is the parser using TPG for parsing expressions and building an AST.
class Parser(tpg.Parser):
    r"""
    token int:         '\d+' ;
    token string:      '\"[^\"]*\"' ;
    separator spaces:  '\s+' ;

    START/s ->   Exp/s ;

    Exp/arg -> OrOperation/arg ( 'or' OrOperation/arg2 $arg=Or(arg,arg2)$)*;

    OrOperation/arg -> AndOperation/arg ( 'and' AndOperation/arg2 $arg=And(arg,arg2)$)*;

    AndOperation/arg ->  NotOperation/arg 
                            | 'not' NotOperation/arg2 $arg=Not(arg2)$
                            | 'not' 'not' NotOperation/arg2 $arg=Not(arg2)$;

    NotOperation/arg -> Comparision/arg (
                            '<'     Comparision/arg2     $arg=Less(arg,arg2)$
                        |   '>'     Comparision/arg2    $arg=Greater(arg,arg2)$
                        |   '!='    Comparision/arg2    $arg=NotEqual(arg,arg2)$
                        |   '=='    Comparision/arg2    $arg=Equal(arg,arg2)$)*;

    Comparision/arg -> AddSubOperation/arg (
                            '\+' AddSubOperation/arg2 $arg=Add(arg,arg2)$
                        |   '\-' AddSubOperation/arg2 $arg=Subtract(arg,arg2)$)*;

    AddSubOperation/arg -> MultiDivOperation/arg (
                            '\*' MultiDivOperation/arg2 $arg=Multiply(arg,arg2)$
                        |   '/'  MultiDivOperation/arg2 $arg=Divide(arg,arg2)$)*;

    MultiDivOperation/arg -> SubString/arg ('\[' MultiDivOperation/arg2  '\]' $arg=GetIndexList(arg,arg2)$)*;

    
    SubString/arg -> List/arg;

    List/arg -> IndexOperation/arg
                | '\[' $a=[]$ (Exp/arg2 $a.append(arg2)$ ) ( ',' (Exp/arg3 $a.append(arg3)$ ))* '\]' $arg=DoList(a)$;

    IndexOperation/arg  -> Atom/arg ('\[' Atom/arg2 '\]' $arg=GetChar(arg,arg2)$)*;
    
    Atom/arg -> '\(' Exp/arg '\)'
                | int/i  $arg=Int(int(i))$  
                | string/i  $arg=String(str(i))$
                | '\[' '\]' $a=[]$ $arg=DoList(a)$;                
    """

# This makes a parser object, which acts as a parsing function.
parser = Parser()


# Below is the driver code, which reads in lines, deals with errors, and
# prints the evaluation result if no error occurs.

# Open the input file, and read in lines of the file.
lines = open(sys.argv[1], 'r').readlines()

# For each line in the input file
for l in lines:
    # Uncomment the next line to help testing.  Comment it for submission.
    #print(l, end="")
    try:
        # Try to parse the expression.
        node = parser(l)

        # Try to evaluate the expression.
        result = node.eval()

        # Print the representation of the result.
        print(repr(result))

    # If an exception is rasied, print the appropriate error.
    except tpg.Error:
        print('Parsing Error')

        # Uncomment the next line to re-raise the parsing error,
        # displaying where the error occurs.  Comment it for submission.

        # raise

    except EvalError:
        print('Evaluation Error')

        # Uncomment the next line to re-raise the evaluation error, 
        # displaying where the error occurs.  Comment it for submission.

        # raise

